package fsmap;

use English;
use Sys::Hostname;
use clutils qw(summary_sync_state summary_connection_state get_drbd_dev_status
		summary_valid_data);

$fsmap::last_error=undef;
$fsmap::last_rc=0;

#########################################################################
# The new method creates an object (given the file name), where		#
# each line from the map file is added as parts to the VG, LV, MNT, 	#
# TYPE, MD arrays.							#
# The field COUNT indicates the number of entries read in.		#
#########################################################################

sub last_error_msg {
	return $fsmap::last_error if defined($fsmap::last_error);
	return "?undefined?";
}

#########################################################################
# Load in details from remote system, if possible...			#
#########################################################################

sub _load_remote {
my ($self,$app,$file,$host)=@_;
my ($r,@f2,$cline,@f,$count,$datadetail,$mopts);
my (@files,@nbd_files,@port_files,$csize);

	#################################################################
	# Everything here is done via ssh to the remote host...		#
	#################################################################

	$datadetail=$self->{DATADETAIL};  ## Always drbd at moment
	@files=();

	#################################################################
	# Read in the details from the nbd directory to get those	#
	# resources as well.						#
	#################################################################

	if($datadetail eq "drbd") {
		$r=`ssh $host "ls /etc/cluster/.resources/drbd" 2>&1`;
	}
	chomp $r;
	if($?>>8) {
		$fsmap::last_error="Unable to open DRBD resources directory on host $host";
		$fsmap::last_rc=5;
		return undef;
	}
	@nbd_files=grep { /[0-9]+\.$app\..*/ } (split(/\s+/,$r));

	#################################################################
	# Read in the details from the ports directory to get those	#
	# resources as well.						#
	#################################################################

	$r=`ssh $host "ls /etc/cluster/.resources/ports" 2>&1`;
	chomp $r;
	if($?>>8) {
		$fsmap::last_error="Unable to open PORTS resources directory on host $host";
		$fsmap::last_rc=5;
		return undef;
	}
	@port_files=grep { /[0-9]+\.$app\..*/ } (split(/\s+/,$r));

	$self->{FILE}=$file;
	$self->{VG}=[];
	$self->{LV}=[];
	$self->{MNT}=[];
	$self->{TYPE}=[];
	$self->{OPTS}=[];
	$self->{PORTS}=[];
	$self->{MD}=[];
	$self->{ND}=[];
	$self->{SIZES}=[];
	$self->{HOST}=$host;
	$count=0;
	$r=`ssh $host "cat $file 2>&1"`;
	if($?>>8) {
		$fsmap::last_error="Unable to open fsmap file on host $host";
		$fsmap::last_rc=5;
		return undef;
	}
	@f2=split(/\n/m,$r);
	for (@f2) {
		my (@m,$vg,$lv,$n);

		chomp;
		next if /^#/;
		next if /^$/;
		next if /^\s+$/;
		@f=split(/:/,$_);
		$vg=$f[0]; $lv=$f[1];
		next if scalar(@f)<4;
		if(exists($f[4])) {
			$mopts=$f[4];
		} else {
			$mopts="";
		}
		if(exists($f[5])) {
			$csize=$f[5];
		} else {
			$csize=-1;
		}

		#########################################################
		# The MD data is no longer used (well at least for	#
		# the moment).						#
		#########################################################

		push(@{$self->{MD}},"");

		#########################################################
		# Get the PORT number details for this file system.	#
		#########################################################
		@m = grep {/^[0-9]+\.$app\.$vg\.$lv$/} @port_files;
		if(exists($m[0]) && defined($m[0])) {
			($n)=($m[0] =~ /([0-9]+)\..*/);
			if(defined($n)) {
				push(@{$self->{PORTS}},$n);
			} else {
				push(@{$self->{PORTS}},"");
			}
		} else {
			push(@{$self->{PORTS}},"");
		}

		push(@{$self->{OPTS}},$mopts);

		#########################################################
		# Get the NBD number details for this file system.	#
		#########################################################
		@m = grep {/^[0-9]+\.$app\.$vg\.$lv$/} @nbd_files;
		if(exists($m[0]) && defined($m[0])) {
			($n)=($m[0] =~ /([0-9]+)\..*/);
			if(defined($n)) {
				push(@{$self->{ND}},$n);
			} else {
				push(@{$self->{ND}},"");
			}
		} else {
			push(@{$self->{ND}},"");
		}

		push(@{$self->{VG}},$f[0]);
		push(@{$self->{LV}},$f[1]);
		push(@{$self->{MNT}},$f[2]);
		push(@{$self->{TYPE}},$f[3]);
		push(@{$self->{SIZES}},$csize);
		$count++;
	}
	$self->{COUNT}=$count;
	return $self;
}

sub new {
my $proto=shift;
my %args=@_;
my $class=ref($proto) || $proto;
my $self={};
my ($fd,$cline,@f,$count,$mopts);
my ($dfd,@nbd_files,@port_files,$app,$csize);

	if(!exists($args{FILE}) || !exists($args{APPLICATION}) ||
		!exists($args{DATADETAIL})) {
		$fsmap::last_error="Missing function arguments (FILE,APPLICATION,DATADETAIL)";
		$fsmap::last_rc=1;
		return undef;
	}

	if($args{DATADETAIL} ne "drbd" && $args{DATADETAIL} ne "enbd") {
		$fsmap::last_error="DATADETAIL must be drbd or enbd";
		$fsmap::last_rc=1;
		return undef;
	}
	$self->{DATADETAIL}=$args{DATADETAIL};
	if(!exists($args{HOST}) || $args{HOST} eq hostname) {
		if(! -f $args{FILE}) {
			$fsmap::last_error="File specified does not exist, or not regular file";
			$fsmap::last_rc=2;
			return undef;
		}

		if(!open($fd,$args{FILE})) {
			$fsmap::last_error="Unable to open specified file";
			$fsmap::last_rc=3;
			return undef;
		}
	} else {
		bless($self,$class);
		return _load_remote($self,$args{APPLICATION},$args{FILE},$args{HOST});
	}
	
	$app=$args{APPLICATION};
	$self->{APPLICATION}=$app;

	#################################################################
	# Read in the details from the nbd directory to get those	#
	# resources as well.						#
	# If datadetail is drbd we instead read that directory.		#
	#################################################################

	if(!opendir($dfd,"/etc/cluster/.resources/drbd")) {
		$fsmap::last_error="Unable to open local DRBD resources directory";
		$fsmap::last_rc=5;
		return undef;
	}
	@nbd_files=grep { /[0-9]+\.$app\..*/ } readdir $dfd;
	closedir($dfd);

	#################################################################
	# Read in the details from the ports directory to get those	#
	# resources as well.						#
	#################################################################

	if(!opendir($dfd,"/etc/cluster/.resources/ports")) {
		$fsmap::last_error="Unable to open local PORTS resources directory";
		$fsmap::last_rc=5;
		return undef;
	}
	@port_files=grep { /[0-9]+\.$app\..*/ } readdir $dfd;
	closedir($dfd);

	$self->{FILE}=$args{FILE};
	$self->{VG}=[];
	$self->{LV}=[];
	$self->{MNT}=[];
	$self->{TYPE}=[];
	$self->{OPTS}=[];
	$self->{PORTS}=[];
	$self->{MD}=[];
	$self->{ND}=[];
	$self->{SIZES}=[];
	$self->{HOST}=hostname;
	$count=0;
	$/="\n";	# is this getting set somewhere?
	while(<$fd>) {
		my (@m,$vg,$lv,$n);

		chomp;
		next if /^#/;
		next if /^$/;
		next if /^\s+$/;
		@f=split(/:/,$_);
		$vg=$f[0]; $lv=$f[1];
		next if scalar(@f)<4;
		if(exists($f[4])) {
			$mopts=$f[4];
		} else {
			$mopts="";
		}
		if(exists($f[5])) {
			$csize=$f[5];
		} else {
			$csize=-1;
		}

		#########################################################
		# Get the RAID number details for this file system.	#
		#########################################################
		@m = grep {/^[0-9]+\.$app\.$vg\.$lv$/} @files;
		if(exists($m[0]) && defined($m[0])) {
			($n)=($m[0] =~ /([0-9]+)\..*/);
			if(defined($n)) {
				push(@{$self->{MD}},$n);
			} else {
				push(@{$self->{MD}},"");
			}
		} else {
			push(@{$self->{MD}},"");
		}

		#########################################################
		# Get the PORT number details for this file system.	#
		#########################################################
		@m = grep {/^[0-9]+\.$app\.$vg\.$lv$/} @port_files;
		if(exists($m[0]) && defined($m[0])) {
			($n)=($m[0] =~ /([0-9]+)\..*/);
			if(defined($n)) {
				push(@{$self->{PORTS}},$n);
			} else {
				push(@{$self->{PORTS}},"");
			}
		} else {
			push(@{$self->{PORTS}},"");
		}

		push(@{$self->{OPTS}},$mopts);

		#########################################################
		# Get the NBD number details for this file system.	#
		#########################################################
		@m = grep {/^[0-9]+\.$app\.$vg\.$lv$/} @nbd_files;
		if(exists($m[0]) && defined($m[0])) {
			($n)=($m[0] =~ /([0-9]+)\..*/);
			if(defined($n)) {
				push(@{$self->{ND}},$n);
			} else {
				push(@{$self->{ND}},"");
			}
		} else {
			push(@{$self->{ND}},"");
		}

		push(@{$self->{VG}},$f[0]);
		push(@{$self->{LV}},$f[1]);
		push(@{$self->{MNT}},$f[2]);
		push(@{$self->{TYPE}},$f[3]);
		push(@{$self->{SIZES}},$csize);
		$count++;
	}
	close($fd);
	$self->{COUNT}=$count;
	bless($self,$class);
	return $self;
}

sub get {
my $self=shift;
my %args=@_;
my @r;

	if(!exists($args{RECNO})) {
		$fsmap::last_error="Missing function arguments";
		$fsmap::last_rc=1;
		return undef;
	}

	if($args{RECNO}>=$self->{COUNT}) {
		$fsmap::last_error="No such record exists";
		$fsmap::last_rc=1;
		return undef;
	}

	$c=$args{RECNO};
	return ($self->{VG}->[$c], $self->{LV}->[$c], 
		$self->{MNT}->[$c], $self->{TYPE}->[$c],
		$self->{MD}->[$c],$self->{ND}->[$c],
		$self->{OPTS}->[$c],$self->{PORTS}->[$c],
		$self->{SIZES}->[$c]);
}

#########################################################################
# Will return a string of RAID device numbers used for the fsmap	#
# entries...								#
#########################################################################

sub get_md_list {
my $self=shift;

	return join(",",@{$self->{MD}});
}

#########################################################################
# Given an MD argument will return a list consisting of the vg, lv, 	#
# mnt, type, md and nd for this entry or undefined if not found.	#
#########################################################################

sub get_md_entry {
my $self=shift;
my %args=@_;
my $count;

	if(!exists($args{MD})) {
		$fsmap::last_error="Missing function arguments";
		$fsmap::last_rc=1;
		return undef;
	}

	$count=0;
	while($count < $self->{COUNT}) {
		last if $self->{MD}->[$count] == $args{MD};
		$count++;
	}
	if($count == $self->{COUNT}) {
		return undef;
	}
	return ($self->{VG}->[$count], $self->{LV}->[$count], 
		$self->{MNT}->[$count], $self->{TYPE}->[$count],
		$self->{MD}->[$count],$self->{ND}->[$count],
		$self->{OPTS}->[$count],$self->{PORTS}->[$count],
		$self->{SIZES}->[$count]);
}

# reread - shouldn't be being used - so remove soon...

sub _reread {
my $self=shift;
my ($fd,$cline,@f,$mopts,$count);
my ($dfd,@port_files,@nbd_files,@files,$app);

	if($self->{HOST} ne hostname) {
		$fsmap::last_error="Method not supported on remote information.";
		$fsmap::last_rc=10;
		return 0;
	}
	if(!open($fd,$self->{FILE})) {
		$fsmap::last_error="Unable to open specified file";
		$fsmap::last_rc=3;
		return 0;
	}
	if(!opendir($dfd,"/etc/cluster/.resources/raid")) {
		$fsmap::last_error="Unable to open local raid resources directory";
		$fsmap::last_rc=5;
		return 0;
	}
	$app=$self->{APPLICATION};
	@files=grep {/[0-9]+\.$app\..*/} readdir $dfd;
	closedir($dfd);

	#################################################################
	# Read in the details from the nbd directory to get those	#
	# resources as well.						#
	#################################################################

	if(!opendir($dfd,"/etc/cluster/.resources/nbd")) {
		$fsmap::last_error="Unable to open local NBD resources directory";
		$fsmap::last_rc=5;
		return undef;
	}
	@nbd_files=grep { /[0-9]+\.$app\..*/ } readdir $dfd;
	closedir($dfd);

	#################################################################
	# Read in the details from the port directory to get those	#
	# resources as well.						#
	#################################################################

	if(!opendir($dfd,"/etc/cluster/.resources/ports")) {
		$fsmap::last_error="Unable to open local PORT resources directory";
		$fsmap::last_rc=5;
		return undef;
	}
	@port_files=grep { /[0-9]+\.$app\..*/ } readdir $dfd;
	closedir($dfd);

	$self->{MD}=[];
	$self->{VG}=[];
	$self->{LV}=[];
	$self->{MNT}=[];
	$self->{OPTS}=[];
	$self->{PORTS}=[];
	$self->{TYPE}=[];
	$self->{ND}=[];
	$count=0;
	while(<$fd>) {
		my (@m,$vg,$lv,$n);

		chomp;
		next if /^#/;
		next if /^$/;
		next if /^\s+$/;
		@f=split(/:/,$_);
		$vg=$f[0]; $lv=$f[1];
		next if scalar(@f)<4;
		if(exists($f[4])) {
			$mopts=$f[4];
		} else {
			$mopts="";
		}

		#########################################################
		# Get the RAID number details for this file system.	#
		#########################################################
		@m = grep {/^[0-9]+\.$app\.$vg\.$lv$/} @files;
		if(exists($m[0]) && defined($m[0])) {
			($n)=($m[0] =~ /([0-9]+)\..*/);
			if(defined($n)) {
				push(@{$self->{MD}},$n);
			} else {
				push(@{$self->{MD}},"");
			}
		} else {
			push(@{$self->{MD}},"");
		}

		#########################################################
		# Get the PORT number details for this file system.	#
		#########################################################
		@m = grep {/^[0-9]+\.$app\.$vg\.$lv$/} @port_files;
		if(exists($m[0]) && defined($m[0])) {
			($n)=($m[0] =~ /([0-9]+)\..*/);
			if(defined($n)) {
				push(@{$self->{PORTS}},$n);
			} else {
				push(@{$self->{PORTS}},"");
			}
		} else {
			push(@{$self->{PORTS}},"");
		}

		#########################################################
		# Get the NBD number details for this file system.	#
		#########################################################
		@m = grep {/^[0-9]+\.$app\.$vg\.$lv$/} @nbd_files;
		if(exists($m[0]) && defined($m[0])) {
			($n)=($m[0] =~ /([0-9]+)\..*/);
			if(defined($n)) {
				push(@{$self->{ND}},$n);
			} else {
				push(@{$self->{ND}},"");
			}
		} else {
			push(@{$self->{ND}},"");
		}
		push(@{$self->{OPTS}},$mopts);
		push(@{$self->{VG}},$f[0]);
		push(@{$self->{LV}},$f[1]);
		push(@{$self->{MNT}},$f[2]);
		push(@{$self->{TYPE}},$f[3]);
		$count++;
	}
	close($fd);
	$self->{COUNT}=$count;
	$fsmap::last_error="OK";
	$fsmap::last_rc=0;
	return 1;
}

sub count {
my $self=shift;

	return $self->{COUNT};
}

#########################################################################
# This is currently only implemented for drbd devices, but will return	#
# the information in the same format as returned by the --status	#
# option for the getmdlist utility.					#
#########################################################################

sub get_sync_state {
my $self=shift;
my %args=@_;
my $recno=0;
my ($vg,$x,$lv,$loop,$found,$ref);

	if(exists($args{RECNO})) {
		$recno=$args{RECNO};
		if(!exists($self->{VG}[$recno])) {
			$fsmap::last_error="Can not find volume group/volume.";
			$fsmap::last_rc=2;
			return undef;
		}
	} else {
		if(!exists($args{VG}) || !exists($args{LV})) {
			$fsmap::last_error="Missing mandatory VG/LV or RECNO arguments.";
			$fsmap::last_rc=1;
			return undef;
		}
		$vg=$args{VG}; $lv=$args{LV};
		$loop=$found=0;
		while($loop < scalar(@{$self->{VG}})) {
			if($self->{VG}[$loop] eq $vg &&
				$self->{LV}[$loop] eq $lv) {
				$found=1; last;
			}
			$loop++;
		}
		if(!$found) {
			$fsmap::last_error="Can not find volume group/volume.";
			$fsmap::last_rc=2;
			return undef;
		}
		$recno=$loop;
	}

	#################################################################
	# recno contains the number of record, so we need to 		#
	# construct the information as necessary.			#
	#################################################################
	$info=$self->{VG}[$recno] . "." . $self->{LV}[$recno];
	$info.=":NA:" . $self->{ND}[$recno] . ":";
	$ref=get_drbd_dev_status($self->{ND}[$recno]);
	if(!defined($ref)) {
		$info.="inactive:unknown:fr1:unknown";
	} else {
		$info.=summary_connection_state($ref) . ":";
		$info.=summary_valid_data($ref) . ":fr1:";
		$x=summary_sync_state($ref);
		$info.=$x;
		if($x eq "syncing") {
			my $y;

			$y=$ref->{total}-$ref->{bad};
			$info.=":$y/" . $ref->{total};
			$info.=":" . $ref->{speed} . ":" . $ref->{est};
		}
	}
	return $info;
}

sub get_fs_match {
my $self=shift;
my %args=@_;
my $fs;
my ($loop,$found);

	if(!exists($args{FILESYSTEM})) {
		$fsmap::last_error="Missing mandatory FILESYSTEM argument.";
		$fsmap::last_rc=1;
		return -1;
	}
	$fs=$args{FILESYSTEM};

	#################################################################
	# Now we scan the information, looking for matches 		#
	#################################################################

	$loop=$found=0;
	while($loop < scalar(@{$self->{MNT}})) {
		if($self->{MNT}[$loop] eq $fs) {
			$found=1; last;
		}
		$loop++;
	}

	if(!$found) {
		$fsmap::last_error="Can not find file system.";
		$fsmap::last_rc=2;
		return -1;
	}
	return $loop;
}

1;

__END__

=head1 NAME

fsmap.pm - Object interface to fsmap application files

=head1 SYNOPSYS

    # Create a new object
    $obj=new fsmap(FILE => "/etc/cluster/.resources/fsmap/apache",
                   APPLICATION => "apache", DATADETAIL => "drbd");

    # Return a count of the number of records read
    $count=$obj->count;

    # Return the volume group, logical volume, mount point, file system
    # type, MD number, NBD/DRBD device number, mount options, the port and the
    # size of the device of the n'th fsmap entry for application
    @entry=$obj->get(RECNO => 0);

    # Return a list of RAID device NUMBERS (if using enbd)
    @raiddevs=$obj->get_md_list;

    # Return the volume group, logical volume, mount point, file system
    # type, MD number (again), NBD/DRBD device number, mount options, port 
    # and size for a specified raid device NUMBER
    ($vg,$lv,$mnt,$fstype,$mdnum,$ndnum,$mopts,$port,$size)=
        $obj->get_md_entry(MD => $raiddevs[0]);

    # Get record number to read based on mount point
    $recno=$obj->get_fs_match(FILESYSTEM => $mntpoint);
    if($recno>=0) {
	    @entry=$obj->get(RECNO => $recno);
    }

=head1 GLOBAL VARIABLES

This module makes use of the following global variables to pass information
back to the caller - information can is available even if the object
creation failed.

=item B<fsmap::last_error>

This contains a text string containing the status of the last call to this
module, such as "OK", or some error text.

=item B<fsmap::last_rc>

A numeric error code from the last call to this module, typically 0 if no
problems were encountered.

=head1 DESCRIPTION

This module contains a series of routines to read in the contents of the
FSMAP details for a particular application and cross reference them with
the RAID and NBD devices, making it easier to handle reporting, 
synchronisation and general management of the of the devices used for an
application.

=head1 Supported Methods

=over 4

=item B<new(FILE => "location", APPLICATION => "apache")>

The B<new> call is used to create a new fsmap object - it will only return
a valid object if the specified file exists, the entries are of the correct
format, and all necessary resources for the NBD/ENBD and RAID devices
can be found.

=item B<count>

This simply returns the number of entries read from the FSMAP file - this
corresponds to the number of logical volumes that form the specified
application that this FSMAP object represents.

=item B<get(RECNO =\> num)>

Return a list consisting of the details for the specified numbered record
in the FSMAP. The number should be from 0 to 1 less than the value returned
by the B<count> method for the specified object.

The list return consists of the following scalar values; 
Volume group, Logical volume, Mount point, File system type, MD number.
DRBD device number, mount points, port and size of the device.

=item B<get_md_list>

This will simply return a list of RAID device numbers for all FSMAP
entries. These numbers can then be used by other code to check the
validity of mirroring of the device if necessary.

=item B<get_md_entry(MD =\> raiddev)>

This will return the same list of details shown for the B<get> method
above, but will return it for the FSMAP entry that uses the specified
RAID device.

=item B<get_fs_match(FILESYSTEM =\> mountpoint)>

This returns the number of the record containing details for the specified
application, or -1 if such a record does not exist.

=item B<get_sync_state(RECNO =\> recnumber)>
=item B<get_sync_state(VG =\> vg, LV =\> lv)>

This routine returns a text string with the following format and can be
used to understand whether the specified record is synchronised, awaiting
synchronisation or currently performing synchronisation. The format of
the returned string is

VG.LV:NA:drbd_device:[inactive|active]:fr1:[sync|unsync|syncing]

